package com.junchenrobot.mes.core;

import com.junchenrobot.mes.exceptions.PLCException;
import com.sourceforge.snap7.moka7.S7;
import com.sourceforge.snap7.moka7.S7Client;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.util.Objects;
import java.util.StringJoiner;

/**
 * DB块读写
 * 读写PLC DB块
 *
 * @author SeaWave
 * @date 2023/01/13
 */
@Slf4j
@Getter
@Setter
public class DBReadAndWrite {
    
    /**
     * 连接plc
     *
     * @param host 主机地址
     * @param port 端口
     * @return {@link S7Client}
     * @throws PLCException PLC异常
     */
    public S7Client connectPLC(String host, int port) throws PLCException {
        if (host == null) {
            throw new PLCException("未设置主机地址");
        }
        S7Client s7Client = new S7Client();
        int connect = s7Client.ConnectTo(host, 0, 0, port);
        if (connect != 0) {
            log.info("连接plc失败!");
            throw new PLCException("连接失败: " + S7Client.ErrorText(s7Client.LastError));
        }
        log.info("连接客户端plc");
        return s7Client;
    }
    
    
    /**
     * 获取某一位的值
     *
     * @param data  数据
     * @param index 索引
     * @return boolean
     */
    public boolean getBit(byte[] data, int index) {
        return S7.GetBitAt(data, 0, index);
    }
    /**
     * 获取某一位的值
     *
     * @param data  数据
     * @param index 索引
     * @return boolean
     */
    public Integer getInt(byte[] data, int index) {
        return S7.GetDIntAt(data, index);
    }

    
    /**
     * 设置值
     *
     * @param data  数据
     * @param index 索引
     * @param value 值
     * @return {@link byte[]}
     */
    public byte[] setBit(byte[] data, int index, boolean value) {
        S7.SetBitAt(data, 0, index, value);
        return data;
    }
    
    public void setInt(byte[] data, int index, int value) {
        // 修改 纺位号
        S7.SetShortAt(data, index, value);
    }
    
    
    public void setDInt(byte[] data, int index, int value) {
        // 修改 纺位号
        S7.SetDIntAt(data, index, value);
    }
    
    
    /**
     * 读取Bite数据
     * 读取DB块中Bool类型的数据
     * 只读取1个字节的数据
     *
     * @param client  客户端
     * @param offset  数据偏移量
     * @param address DB块地址
     * @return {@link byte[]}
     * @throws PLCException PLC异常
     */
    public byte[] readBitData(S7Client client, int address, int offset) throws PLCException {
        if (Objects.isNull(client)) {
            throw new PLCException("客户端未连接");
        }
        // 初始化数据容器
        byte[] data = new byte[1];
        int readArea = client.ReadArea(S7.S7AreaDB, address, offset, 1, data);
        if (readArea != 0) {
            throw new PLCException("读取失败: " + S7Client.ErrorText(client.LastError));
        }   // 打印日志
        printByteLog(address, offset, 0, data);
        return data;
    }
    
    
    /**
     * 写入Bite数据
     * 写入DB块中Bool类型的数据
     * 只写入个字节的数据
     *
     * @param client  客户端
     * @param offset  数据偏移量
     * @param address DB块地址
     * @param data    写入的数据
     * @throws PLCException PLC异常
     */
    public void winderBitDB(S7Client client, int address, int offset, byte[] data) throws PLCException {
        if (Objects.isNull(client)) {
            throw new PLCException("客户端未连接");
        }
        int writeArea = client.WriteArea(S7.S7AreaDB, address, offset, 1, data);
        if (writeArea != 0) {
            throw new PLCException("写入失败: " + S7Client.ErrorText(client.LastError));
        }
        // 打印日志
        printByteLog(address, offset, 1, data);
    }
    
    
    /**
     * 读取DB块中Int类型的数据
     * 只读取2个字节的数据
     *
     * @param client  客户端
     * @param offset  数据偏移量
     * @param address 写数据库
     * @throws PLCException PLC异常
     */
    public byte[] readIntData(S7Client client, int address, int offset) throws PLCException {
        if (Objects.isNull(client)) {
            throw new PLCException("客户端未连接");
        }
        // 初始化数据容器
        byte[] data = new byte[2];
        int readArea = client.ReadArea(S7.S7AreaDB, address, offset, 2, data);
        if (readArea != 0) {
            throw new PLCException("读取失败: " + S7Client.ErrorText(client.LastError));
        }
        printIntLog(address, offset, 0, data);
        return data;
    }
    
    public byte[] readData(S7Client client, int address, int offset, int len) throws PLCException {
        if (Objects.isNull(client)) {
            throw new PLCException("客户端未连接");
        }
        // 初始化数据容器
        byte[] data = new byte[len];
        int readArea = client.ReadArea(S7.S7AreaDB, address, offset, len, data);
        if (readArea != 0) {
            throw new PLCException("读取失败: " + S7Client.ErrorText(client.LastError));
        }
        printIntLog(address, offset, 0, data);
        return data;
    }
    
    public void writeData(S7Client client, int address, int offset, int len, byte[] data) throws PLCException {
        if (Objects.isNull(client)) {
            throw new PLCException("客户端未连接");
        }
        int writeArea = client.WriteArea(S7.S7AreaDB, address, offset, len, data);
        if (writeArea != 0) {
            throw new PLCException("写入失败: " + S7Client.ErrorText(client.LastError));
        }
        // 打印日志
        printIntLog(address, offset, 1, data);
    }
    
    
    /**
     * 写入int数据
     * 写入DB块中Int类型的数据
     * 只写入2个字节的数据
     *
     * @param client  客户端
     * @param address 写入的DB地址
     * @param offset  偏移量
     * @throws PLCException plcexception
     */
    public void writeIntData(S7Client client, int address, int offset, byte[] data) throws PLCException {
        if (Objects.isNull(client)) {
            throw new PLCException("客户端未连接");
        }
        int writeArea = client.WriteArea(S7.S7AreaDB, address, offset, 2, data);
        if (writeArea != 0) {
            throw new PLCException("写入失败: " + S7Client.ErrorText(client.LastError));
        }
        // 打印日志
        printIntLog(address, offset, 1, data);
    }
    
    public byte[] readDIntData(S7Client client, int address, int offset) throws PLCException {
        if (Objects.isNull(client)) {
            throw new PLCException("客户端未连接");
        }
        // 初始化数据容器
        byte[] data = new byte[4];
        int readArea = client.ReadArea(S7.S7AreaDB, address, offset, 4, data);
        if (readArea != 0) {
            throw new PLCException("读取失败: " + S7Client.ErrorText(client.LastError));
        }
        // 打印日志
        printDIntLog(address, offset, 0, data);
        return data;
    }
    
    public void writeDIntData(S7Client client, int address, int offset, byte[] data) throws PLCException {
        if (Objects.isNull(client)) {
            throw new PLCException("客户端未连接");
        }
        int writeArea = client.WriteArea(S7.S7AreaDB, address, offset, 4, data);
        if (writeArea != 0) {
            throw new PLCException("写入失败: " + S7Client.ErrorText(client.LastError));
        }
        // 打印日志
        printDIntLog(address, offset, 1, data);
    }
    
    
    /**
     * 断开连接
     *
     * @param client 客户端
     */
    public void disconnect(S7Client client) {
        log.info("关闭连接客户端plc!");
        client.Disconnect();
    }
    
    
    /**
     * 打印字节日志
     * 只能打印一个字节的日志 适用于Bool类型
     *
     * @param address     DB块地址
     * @param offset      偏移量
     * @param readOrWrite 读或写操作
     * @param data        读或写地址
     */
    public void printByteLog(int address, int offset, int readOrWrite, byte[] data) {
        StringJoiner joiner = joiner(readOrWrite);
        for (int i = 0; i < 8; i++) {
            boolean b = S7.GetBitAt(data, 0, i);
            joiner.add(b ? "1" : "0");
            if (i == 3) {
                // 8位 每四位为一组
                joiner.add(" ");
            }
        }
        log.info("{} {} {}", address, offset, joiner);
    }
    
    
    /**
     * 打印整型日志
     * 只能打印2个字节的日志 适用于Int类型
     *
     * @param address     DB块地址
     * @param offset      偏移量
     * @param readOrWrite 读或写操作
     * @param data        读或写地址
     */
    public void printIntLog(int address, int offset, int readOrWrite, byte[] data) {
        StringJoiner joiner = joiner(readOrWrite);
        int result = S7.GetShortAt(data, 0);
        joiner.add(String.valueOf(result));
        log.info("{} {} {}", address, offset, joiner);
    }
    
    /**
     * 打印大整型日志
     * 只能打印4个字节的日志 适用于DInt类型
     *
     * @param address     DB块地址
     * @param offset      偏移量
     * @param readOrWrite 读或写操作
     * @param data        读或写地址
     */
    public void printDIntLog(int address, int offset, int readOrWrite, byte[] data) {
        StringJoiner joiner = joiner(readOrWrite);
        int result = S7.GetDIntAt(data, 0);
        joiner.add(String.valueOf(result));
        log.info("{} {} {}", address, offset, joiner);
    }
    
    /**
     * 字符串拼接
     * 0 打印读数据
     * 1 打印写数据
     *
     * @param readOrWrite 读或写
     * @return {@link StringJoiner}
     */
    private StringJoiner joiner(int readOrWrite) {
        StringJoiner joiner;
        if (readOrWrite == 0) {
            // read
            joiner = new StringJoiner(" ", "<===[ ", " ]");
        } else {
            // write
            joiner = new StringJoiner(" ", "===>[ ", " ]");
        }
        return joiner;
    }
    
}
